/*
 *  Copyright (c) 2011 Leibniz Institute of Plant Genetics and Crop Plant Research (IPK), Gatersleben, Germany.
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the GNU Lesser Public License v2.1
 *  which accompanies this distribution, and is available at
 *  http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 *
 *  Contributors:
 *      Leibniz Institute of Plant Genetics and Crop Plant Research (IPK), Gatersleben, Germany - initial API and implementation
 */
package de.ipk_gatersleben.bit.bi.edal.primary_data.file.implementation;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.Principal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.SortedSet;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.security.auth.Subject;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Restrictions;
import org.hibernate.stat.Statistics;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.hbm2ddl.SchemaValidator;

import de.ipk_gatersleben.bit.bi.edal.primary_data.DataManager;
import de.ipk_gatersleben.bit.bi.edal.primary_data.EdalConfiguration;
import de.ipk_gatersleben.bit.bi.edal.primary_data.EdalConfigurationException;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.EdalException;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.ImplementationProvider;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.PrimaryDataDirectory;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.PrimaryDataDirectoryException;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.PrimaryDataEntity;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.PrimaryDataEntityVersion;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.PrimaryDataEntityVersionException;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.PrimaryDataFile;
import de.ipk_gatersleben.bit.bi.edal.primary_data.file.PublicReference;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.ApprovalServiceProvider;
import de.ipk_gatersleben.bit.bi.edal.primary_data.reference.PersistentIdentifier;
import de.ipk_gatersleben.bit.bi.edal.primary_data.security.PermissionProvider;

/**
 * FileSystem implementation of {@link ImplementationProvider}
 * 
 * @author arendd
 */
public class FileSystemImplementationProvider implements ImplementationProvider {

	private static final String EDALDB_DBNAME = "edaldb";

	private Logger logger = null;

	private static final int SQL_ERROR_DATABASE_IN_USE = 90020;

	private static final int SQL_ERROR_DATABASE_NOT_FOUND = 90013;

	private boolean autoIndexing;

	private EdalConfiguration configuration;
	private Connection connection = null;
	private String h2Pass;

	private String h2User;
	private IndexWriterThread indexThread = null;
	private InternetAddress rootUserAddress = null;
	private SessionFactory sessionFactory = null;

	public FileSystemImplementationProvider(EdalConfiguration configuration) {

		this.configuration = configuration;

		try {
			this.setRootUserAddress(configuration.getRootUser());
			this.setH2User(this.getConfiguration().getH2UserName());
			this.setH2Pass(this.getConfiguration().getH2Password());
		} catch (EdalConfigurationException e) {
			// should never happen, because of the validation function
			e.printStackTrace();
		}

		this.logger = configuration.getLogger();

		this.setAutoIndexing(autoIndexing);

		try {
			try {
				Class.forName("org.h2.Driver");
				this.setConnection(DriverManager.getConnection("jdbc:h2:"
						+ this.getMountPath()
						+ ";IFEXISTS=TRUE;DB_CLOSE_ON_EXIT=FALSE;LOCK_MODE=0",
						this.getH2User(), this.getH2Pass()));
			} catch (final ClassNotFoundException e) {
				this.getLogger().error(
						"Could not find driver for H2 connection !");
				System.exit(0);
			}

		} catch (final SQLException se) {

			if (se.getErrorCode() == SQL_ERROR_DATABASE_IN_USE) {
				this.getLogger().warn(
						"Database still in use -> close and restart please !");
				System.exit(0);
			}
			if (se.getErrorCode() == SQL_ERROR_DATABASE_NOT_FOUND) {
				this.getLogger().info(
						"No database found -> creating new database...");
				try {
					this.setConnection(DriverManager.getConnection("jdbc:h2:"
							+ this.getMountPath()
							+ ";DB_CLOSE_ON_EXIT=FALSE;LOCK_MODE=0",
							this.getH2User(), this.getH2Pass()));

				} catch (final SQLException e) {
					this.getLogger().error("Could not start H2 connection !");
					System.exit(0);
				}
			}
		}

		final Configuration config = new Configuration();

		config.configure(FileSystemImplementationProvider.class
				.getResource("hibernate.cfg.xml"));

		config.setProperty("hibernate.connection.url",
				"jdbc:h2:" + this.getMountPath());
		config.setProperty("hibernate.connection.username", this.getH2User());
		config.setProperty("hibernate.connection.password", this.getH2Pass());
		config.setProperty("hibernate.search.default.indexBase",
				Paths.get(this.getMountPath().toString(), "lucene").toString());

		if (!this.isAutoIndexing()) {
			config.setProperty("hibernate.search.indexing_strategy", "manual");
		}

		Boolean exists = false;
		try (ResultSet result = this.getConnection().createStatement()
				.executeQuery("SELECT count(*) FROM ENTITIES ")) {
			result.last();
			final int resultSize = result.getInt("COUNT(*)");
			if (resultSize > 0) {
				exists = true;
			}
			result.close();
		} catch (final SQLException e) {
			exists = false;
		}

		if (!exists) {
			new SchemaExport(config).create(false, true);
			try {
				this.setSessionFactory(config.buildSessionFactory());
			} catch (HibernateException e) {
				logger.error("Lucene Index damaged", e);
				logger.info("Lucene Index damaged -> clean up index directory to rebuild the index !");
				System.exit(0);
			}
			// /*
			// * to check if the PermissionCheck for normal users save another
			// * root-users
			// */
			// final Session session = this.getSessionFactory().openSession();
			// final Transaction transaction = session.beginTransaction();
			// try {
			// session.save(new RootImplementation("D", "A",
			// new InternetAddress("edal-root@ipk-gatersleben.de")));
			// } catch (AddressException e) {
			// e.printStackTrace();
			// }
			// transaction.commit();
			// session.close();
			// /* ********************************************************** */

		} else {
			try {
				this.setSessionFactory(config.buildSessionFactory());
			} catch (HibernateException e) {
				logger.debug("Lucene Index damaged", e);
				logger.info("Lucene Index damaged -> clean up index directory to rebuild the index !");
				System.exit(0);
			}
			/* validate database schema */
			try {
				new SchemaValidator(config).validate();
				this.getLogger().info("database schema is valid");

			} catch (final HibernateException e) {
				this.getLogger().error(
						"Found existing, but not compatible data schema in path '"
								+ configuration.getMountPath() + "' ("
								+ e.getMessage() + ") ");
				this.getLogger().error(
						"Please delete path or specify another mount path !");
				System.exit(0);
			}
		}
		/* enable statistics log of the SessionFactory */
		this.getSessionFactory().getStatistics().setStatisticsEnabled(true);

		if (!this.isAutoIndexing()) {
			this.setIndexThread(new IndexWriterThread(this.getSessionFactory()));
			this.getIndexThread().start();
		}

	}

	/** {@inheritDoc} */
	@Override
	public MetaDataImplementation createMetaDataInstance() {
		return new MetaDataImplementation();
	}

	/** {@inheritDoc} */
	@Override
	public Class<? extends ApprovalServiceProvider> getApprovalServiceProvider() {

		return ApprovalServiceProviderImplementation.class;
	}

	/** {@inheritDoc} */
	@Override
	public Long getAvailableStorageSpace() throws EdalException {

		if (this.getDataPath() == null
				|| Files.notExists(this.getDataPath(),
						LinkOption.NOFOLLOW_LINKS)) {
			throw new EdalException(
					"No mount path defined, please run getRootDirectory first");
		}
		try {
			return Files.getFileStore(this.getDataPath()).getUsableSpace();
		} catch (final IOException e) {
			throw new EdalException("Unable to request available space", e);
		}
	}

	/** {@inheritDoc} */
	@Override
	public EdalConfiguration getConfiguration() {
		return this.configuration;
	}

	/**
	 * Getter for the database {@link Connection}.
	 * 
	 * @return a {@link Connection} object.
	 */
	private Connection getConnection() {
		return this.connection;
	}

	/**
	 * Private getter for the dataPath
	 * 
	 * @return the dataPath
	 */
	public Path getDataPath() {
		return Paths.get(this.getConfiguration().getDataPath().toString(),
				FileSystemImplementationProvider.EDALDB_DBNAME);
	}

	/**
	 * Getter for the database password.
	 * 
	 * @return the h2Pass
	 */
	private String getH2Pass() {
		return this.h2Pass;
	}

	/**
	 * Getter for the database user.
	 * 
	 * @return the h2User
	 */
	private String getH2User() {
		return this.h2User;
	}

	/**
	 * @return the indexThread
	 */
	private IndexWriterThread getIndexThread() {
		return this.indexThread;
	}

	/** {@inheritDoc} */
	@Override
	public Logger getLogger() {
		return this.logger;
	}

	/**
	 * Getter for the mount path of the EDAL system.
	 * 
	 * @return the current MountPath.
	 */
	public Path getMountPath() {
		return Paths.get(this.getConfiguration().getMountPath().toString(),
				FileSystemImplementationProvider.EDALDB_DBNAME);
	}

	/** {@inheritDoc} */
	@Override
	public Class<? extends PermissionProvider> getPermissionProvider() {

		return PermissionProviderImplementation.class;
	}

	/** {@inheritDoc} */
	@Override
	public InternetAddress getPreviousRootUser() throws EdalException {

		Session session = this.getSession();

		RootImplementation rootUser = (RootImplementation) session
				.createCriteria(RootImplementation.class).uniqueResult();

		session.close();

		if (rootUser == null) {
			return null;
		}

		else {
			InternetAddress address = null;
			try {
				address = new InternetAddress(rootUser.getAddress());
			} catch (AddressException | NullPointerException e) {
				// throw new EdalException("unable to load email address: "
				// + e.getMessage(), e);
				return null;
			}
			return address;
		}

	}

	/** {@inheritDoc} */
	@Override
	public Class<? extends PrimaryDataDirectory> getPrimaryDataDirectoryProvider() {

		return PrimaryDataDirectoryImplementation.class;
	}

	/** {@inheritDoc} */
	@Override
	public PrimaryDataEntity getPrimaryDataEntityByID(final String uuid,
			final long versionNumber) throws EdalException {

		final Session session = this.getSessionFactory().openSession();

		final Criteria getFile = session
				.createCriteria(PrimaryDataFileImplementation.class)
				.add(Restrictions.eq("class",
						PrimaryDataFileImplementation.class))
				.add(Restrictions.eq("ID", uuid));

		final PrimaryDataFile file = (PrimaryDataFile) getFile.uniqueResult();

		if (file == null) {
			final Criteria getDirectory = session
					.createCriteria(PrimaryDataDirectoryImplementation.class)
					.add(Restrictions.eq("class",
							PrimaryDataDirectoryImplementation.class))
					.add(Restrictions.eq("ID", uuid));

			final PrimaryDataDirectory directory = (PrimaryDataDirectory) getDirectory
					.uniqueResult();

			if (directory == null) {
				throw new EdalException("found no entity with ID '" + uuid
						+ "'");
			} else {

				PrimaryDataEntityVersion version;
				try {
					version = directory
							.getVersionByRevisionNumber(versionNumber);
				} catch (PrimaryDataEntityVersionException e) {
					/** found no version with this number */
					throw new EdalException(e.getMessage(), e);
				}

				try {
					directory.switchCurrentVersion(version);
				} catch (PrimaryDataEntityVersionException e) {
					throw new EdalException(
							"unable to switch the version with the number "
									+ versionNumber, e);
				}

			}

			final List<PublicReference> list = directory.getCurrentVersion()
					.getPublicReferences();

			for (final PublicReference publicReference : list) {
				if (publicReference.isPublic()) {
					if (publicReference.getReleaseDate() == null) {
						return directory;
					} else {
						if (publicReference.getReleaseDate().after(
								Calendar.getInstance())) {
							throw new EdalException(
									"the PublicReference for this version of "
											+ directory.getName()
											+ " is locked until "
											+ publicReference.getReleaseDate()
													.getTime());
						} else {
							return directory;
						}
					}
				}
			}
			throw new EdalException(
					"found no PublicReference for this version of "
							+ directory.getName());

		} else {

			PrimaryDataEntityVersion version;
			try {
				version = file.getVersionByRevisionNumber(versionNumber);
			} catch (PrimaryDataEntityVersionException e) {
				/** found no version with this number */
				throw new EdalException(e.getMessage(), e);
			}

			try {
				file.switchCurrentVersion(version);
			} catch (PrimaryDataEntityVersionException e) {
				throw new EdalException(
						"unable to switch the version with the number "
								+ versionNumber, e);
			}

			final List<PublicReference> list = file.getCurrentVersion()
					.getPublicReferences();
			for (final PublicReference publicReference : list) {
				if (publicReference.isPublic()) {

					if (publicReference.getReleaseDate() == null) {
						return file;
					} else {
						if (publicReference.getReleaseDate().after(
								Calendar.getInstance())) {
							throw new EdalException(
									"the PublicReference for this version of "
											+ file.getName()
											+ " is locked until "
											+ publicReference.getReleaseDate()
													.getTime());
						} else {
							return file;
						}
					}
				}
			}
			throw new EdalException(
					"found no PublicReference for this version of "
							+ file.getName());
		}

	}

	/** {@inheritDoc} */
	@Override
	public PrimaryDataEntity reloadPrimaryDataEntityByID(final String uuid,
			final long versionNumber) throws EdalException {

		final Session session = this.getSessionFactory().openSession();

		final Criteria getFile = session
				.createCriteria(PrimaryDataFileImplementation.class)
				.add(Restrictions.eq("class",
						PrimaryDataFileImplementation.class))
				.add(Restrictions.eq("ID", uuid));

		final PrimaryDataFile file = (PrimaryDataFile) getFile.uniqueResult();

		if (file == null) {
			final Criteria getDirectory = session
					.createCriteria(PrimaryDataDirectoryImplementation.class)
					.add(Restrictions.eq("class",
							PrimaryDataDirectoryImplementation.class))
					.add(Restrictions.eq("ID", uuid));

			final PrimaryDataDirectory directory = (PrimaryDataDirectory) getDirectory
					.uniqueResult();

			if (directory == null) {
				session.close();
				throw new EdalException("found no entity with ID '" + uuid
						+ "'");
			} else {
				PrimaryDataEntityVersion version;
				try {
					version = directory
							.getVersionByRevisionNumber(versionNumber);
				} catch (PrimaryDataEntityVersionException e) {
					session.close();
					throw new EdalException(e.getMessage(), e);
				}

				try {
					directory.switchCurrentVersion(version);
				} catch (PrimaryDataEntityVersionException e) {
					session.close();
					throw new EdalException(
							"unable to switch the version with the number "
									+ versionNumber, e);
				}
			}
			session.close();
			return directory;
		} else {

			PrimaryDataEntityVersion version;
			try {
				version = file.getVersionByRevisionNumber(versionNumber);
			} catch (PrimaryDataEntityVersionException e) {
				session.close();
				throw new EdalException(e.getMessage(), e);
			}

			try {
				file.switchCurrentVersion(version);
			} catch (PrimaryDataEntityVersionException e) {
				session.close();
				throw new EdalException(
						"unable to switch the version with the number "
								+ versionNumber, e);
			}
			session.close();
			return file;
		}
	}

	@Override
	public PrimaryDataEntity getPrimaryDataEntityForPersistenIdentifier(
			String uuid, long versionNumber,
			PersistentIdentifier persistentIdentifier) throws EdalException {

		final Session session = this.getSessionFactory().openSession();

		PrimaryDataFile file = (PrimaryDataFile) session
				.createCriteria(PrimaryDataFileImplementation.class)
				.add(Restrictions.eq("class",
						PrimaryDataFileImplementation.class))
				.add(Restrictions.eq("ID", uuid)).uniqueResult();

		if (file == null) {

			PrimaryDataDirectory directory = (PrimaryDataDirectory) session
					.createCriteria(PrimaryDataDirectoryImplementation.class)
					.add(Restrictions.eq("class",
							PrimaryDataDirectoryImplementation.class))
					.add(Restrictions.eq("ID", uuid)).uniqueResult();

			if (directory == null) {
				session.close();
				throw new EdalException("no entity with ID '" + uuid
						+ "' found !");
			} else {

				try {
					PublicReference reference = directory
							.getVersionByRevisionNumber(versionNumber)
							.getPublicReference(persistentIdentifier);
					session.close();
					if (reference.isPublic()) {
						if (reference.getReleaseDate() == null) {
							return directory;
						} else {
							if (reference.getReleaseDate().after(
									Calendar.getInstance())) {
								throw new EdalException(
										"the PublicReference for this version of "
												+ directory.getName()
												+ " is locked until "
												+ reference.getReleaseDate()
														.getTime());
							} else {
								return directory;
							}
						}

					} else {
						return searchRekursiveForPersistentIdentifiers(session,
								directory, versionNumber, persistentIdentifier);
					}
				} catch (PrimaryDataEntityVersionException e) {
					return searchRekursiveForPersistentIdentifiers(session,
							directory, versionNumber, persistentIdentifier);
				}
			}
		} else {

			try {
				PublicReference reference = file.getVersionByRevisionNumber(
						versionNumber).getPublicReference(persistentIdentifier);
				session.close();
				if (reference.isPublic()) {
					if (reference.getReleaseDate() == null) {
						return file;
					} else {
						if (reference.getReleaseDate().after(
								Calendar.getInstance())) {
							throw new EdalException(
									"the PublicReference for this version of "
											+ file.getName()
											+ " is locked until "
											+ reference.getReleaseDate()
													.getTime());
						} else {
							return file;
						}
					}
				} else {
					return searchRekursiveForPersistentIdentifiers(session,
							file, versionNumber, persistentIdentifier);
				}
			} catch (PrimaryDataEntityVersionException e) {
				return searchRekursiveForPersistentIdentifiers(session, file,
						versionNumber, persistentIdentifier);
			}
		}
	}

	private PrimaryDataEntity searchRekursiveForPersistentIdentifiers(
			Session session, PrimaryDataEntity entity, long versionNumber,
			PersistentIdentifier persistentIdentifier) throws EdalException {

		PrimaryDataEntityVersion version = null;
		try {
			version = entity.getVersionByRevisionNumber(versionNumber);
		} catch (PrimaryDataEntityVersionException e) {
			session.close();
			throw new EdalException(e.getMessage());
		}
		boolean ready = false;

		PrimaryDataEntity parent = null;

		try {
			parent = entity.getParentDirectory();
			if (parent == null) {
				session.close();
				throw new EdalException("no PublicReference for entity '"
						+ entity.getName() + "' publicated");
			}
		} catch (PrimaryDataDirectoryException e1) {
			session.close();
			throw new EdalException("no PublicReference for entity '"
					+ entity.getName() + "' publicated");
		}
		while (!ready) {

			SortedSet<PrimaryDataEntityVersion> set = parent.getVersions();

			boolean found = false;

			for (PrimaryDataEntityVersion primaryDataEntityVersion : set) {
				try {
					if (primaryDataEntityVersion.getPublicReference(
							persistentIdentifier).isPublic()) {
						if (primaryDataEntityVersion.getRevisionDate().after(
								version.getRevisionDate())) {
							found = true;
						}
					}
				} catch (PrimaryDataEntityVersionException e) {
					DataManager
							.getImplProv()
							.getLogger()
							.debug("no public reference found for '" + parent
									+ "', trying next");
				}
			}

			if (!found) {
				try {
					parent = parent.getParentDirectory();
				} catch (PrimaryDataDirectoryException e) {
					session.close();
					throw new EdalException("no PublicReference for entity '"
							+ entity.getName() + "' publicated");
				}
				if (parent == null) {
					session.close();
					throw new EdalException("no PublicReference for entity '"
							+ entity.getName() + "' publicated");
				}
			} else {
				session.close();
				ready = true;
			}
		}
		return entity;
	}

	/** {@inheritDoc} */
	@Override
	public PrimaryDataEntity getPrimaryDataEntityForReviewer(String uuid,
			long versionNumber, String internalId, int reviewerCode)
			throws EdalException {

		final Session session = this.getSessionFactory().openSession();

		ReviewersImplementation reviewer = (ReviewersImplementation) session
				.createCriteria(ReviewersImplementation.class)
				.add(Restrictions.eq("hashCode", reviewerCode)).uniqueResult();

		if (reviewer != null) {

			PrimaryDataFile file = (PrimaryDataFile) session
					.createCriteria(PrimaryDataFileImplementation.class)
					.add(Restrictions.eq("class",
							PrimaryDataFileImplementation.class))
					.add(Restrictions.eq("ID", uuid)).uniqueResult();

			if (file != null) {
				try {
					PrimaryDataEntityVersion version = file
							.getVersionByRevisionNumber(versionNumber);
					file.switchCurrentVersion(version);
					session.close();
					return file;
				} catch (PrimaryDataEntityVersionException e) {
					session.close();
					throw new EdalException(e.getMessage(), e);
				}
			} else {
				PrimaryDataDirectory directory = (PrimaryDataDirectory) session
						.createCriteria(
								PrimaryDataDirectoryImplementation.class)
						.add(Restrictions.eq("class",
								PrimaryDataDirectoryImplementation.class))
						.add(Restrictions.eq("ID", uuid)).uniqueResult();
				if (directory == null) {
					session.close();
					throw new EdalException("no entity with ID '" + uuid
							+ "' found !");
				} else {
					try {
						PrimaryDataEntityVersion version = directory
								.getVersionByRevisionNumber(versionNumber);
						directory.switchCurrentVersion(version);
						session.close();
						return directory;
					} catch (PrimaryDataEntityVersionException e) {
						session.close();
						throw new EdalException(e.getMessage(), e);
					}
				}
			}
		} else {
			session.close();
			throw new EdalException("no reviewer with ID '" + reviewerCode
					+ "' found !");
		}
	}

	/** {@inheritDoc} */
	@Override
	public Class<? extends PrimaryDataFile> getPrimaryDataFileProvider() {
		return PrimaryDataFileImplementation.class;
	}

	/**
	 * @return the rootUserAddress
	 */
	public InternetAddress getRootUser() {
		return rootUserAddress;
	}

	/**
	 * Getter for a new {@link Session} for public access.
	 * 
	 * <em>NOTE: use {@link Session#close()} after UnitOfWork !</em>
	 * 
	 * @return new Session
	 */
	public Session getSession() {
		return this.getSessionFactory().openSession();
	}

	/**
	 * Private Setter for the {@link SessionFactory}
	 * 
	 * @return the sessionFactory
	 */
	private SessionFactory getSessionFactory() {
		return this.sessionFactory;
	}

	/**
	 * Getter for the {@link Statistics} of the {@link SessionFactory}
	 * 
	 * @return the {@link Statistics}
	 */
	public Statistics getStatistics() {
		return this.getSessionFactory().getStatistics();
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Class<? extends Principal>> getSupportedPrincipals()
			throws EdalException {

		final Session session = this.getSessionFactory().openSession();

		final Criteria principals = session
				.createCriteria(SupportedPrincipals.class);

		final List<SupportedPrincipals> privatePrincipals = principals.list();

		if (privatePrincipals.isEmpty()) {
			throw new EdalException(
					"Unable to load all supported Principals : no type stored");
		}

		final List<Class<? extends Principal>> list = new ArrayList<Class<? extends Principal>>(
				privatePrincipals.size());

		for (final SupportedPrincipals principal : privatePrincipals) {
			try {
				list.add((Class<? extends Principal>) Class.forName(principal
						.getName()));
			} catch (final ClassNotFoundException e) {
				throw new EdalException(
						"Unable to load all supported Principals", e);
			}
		}
		return list;

	}

	/** {@inheritDoc} */
	@Override
	public Long getUsedStorageSpace() throws EdalException {

		if (this.getDataPath() == null
				|| Files.notExists(this.getDataPath(),
						LinkOption.NOFOLLOW_LINKS)) {
			throw new EdalException(
					"No mount path defined, please run getRootDirectory first");
		}
		try {
			final AtomicLong size = new AtomicLong();
			Files.walkFileTree(this.getDataPath(),
					new SimpleFileVisitor<Path>() {
						@Override
						public FileVisitResult visitFile(final Path file,
								final BasicFileAttributes attrs)
								throws IOException {
							size.addAndGet(attrs.size());
							return FileVisitResult.CONTINUE;
						}
					});
			return size.longValue();
		} catch (final IOException e) {
			throw new EdalException("Unable to request used space", e);
		}
	}

	/**
	 * Private getter for the autoIndexing
	 * 
	 * @return the autoIndexing
	 */
	private boolean isAutoIndexing() {
		return this.autoIndexing;
	}

	/** {@inheritDoc} */
	@Override
	public boolean isRootValidated(InternetAddress address) {

		Session session = this.getSession();

		RootImplementation rootUser = (RootImplementation) session
				.createCriteria(RootImplementation.class).uniqueResult();

		session.close();

		if (rootUser.getAddress().equals(address.getAddress())
				&& rootUser.isValidated()) {

			getLogger().info("root user validated");
			return true;
		} else {
			return false;
		}

	}

	/** {@inheritDoc} */
	@Override
	public PrimaryDataDirectory mount(
			final List<Class<? extends Principal>> supportedPrincipals)
			throws PrimaryDataDirectoryException {

		final Session session = this.getSessionFactory().openSession();

		final Criteria checkRoot = session
				.createCriteria(PrimaryDataDirectoryImplementation.class)
				.add(Restrictions.eq("class",
						PrimaryDataDirectoryImplementation.class))
				.add(Restrictions.isNull("parentDirectory"));

		if (checkRoot.uniqueResult() == null) {

			session.close();

			DataManager.getImplProv().getLogger()
					.info("Creating new RootDirectory...");

			final Session sess = this.getSessionFactory().openSession();
			final Transaction transaction = sess.beginTransaction();

			for (final Class<? extends Principal> clazz : supportedPrincipals) {
				sess.save(new SupportedPrincipals(clazz));
			}
			transaction.commit();
			sess.close();
			PrimaryDataDirectory newRootDirectory = null;

			try {
				final Constructor<? extends PrimaryDataDirectory> constructor = DataManager
						.getImplProv()
						.getPrimaryDataDirectoryProvider()
						.getConstructor(PrimaryDataDirectory.class,
								String.class);

				newRootDirectory = constructor.newInstance(null,
						PrimaryDataDirectory.PATH_SEPARATOR);
			} catch (final Exception e) {
				throw new PrimaryDataDirectoryException(
						"Can not instantiate the constructor to mount implementation: "
								+ e.getMessage(), e);
			}

			return newRootDirectory;
		}

		else {

			final Criteria principals = session
					.createCriteria(SupportedPrincipals.class);

			@SuppressWarnings("unchecked")
			final List<SupportedPrincipals> privatePrincipals = principals
					.list();

			final List<SupportedPrincipals> publicPrincipals = new ArrayList<SupportedPrincipals>(
					supportedPrincipals.size());

			for (final Class<? extends Principal> clazz : supportedPrincipals) {
				publicPrincipals.add(new SupportedPrincipals(clazz));
			}
			if (privatePrincipals.containsAll(publicPrincipals)) {
				DataManager.getImplProv().getLogger()
						.info("All principals are supported !");
			} else {
				DataManager
						.getImplProv()
						.getLogger()
						.warn("Not all principals are supported , please define new list and connect again !");
				throw new PrimaryDataDirectoryException(
						"Not all principals are supported , please define new list and connect again !");
			}

			DataManager.getImplProv().getLogger()
					.info("Getting existing RootDirectory...");

			final PrimaryDataDirectoryImplementation existingRootDirectory = (PrimaryDataDirectoryImplementation) checkRoot
					.uniqueResult();

			session.close();

			final PrimaryDataDirectory existingRootDirectoryOrg = existingRootDirectory;

			return existingRootDirectoryOrg;
		}

	}

	/**
	 * Private setter for the autoIndexing
	 * 
	 * @param autoIndexing
	 *            the autoIndexing to set
	 */
	private void setAutoIndexing(final boolean autoIndexing) {
		this.autoIndexing = autoIndexing;
	}

	private void setConnection(final Connection connection) {
		this.connection = connection;
	}

	/**
	 * Private setter for the database password.
	 * 
	 * @param h2Pass
	 *            the h2Pass to set
	 */
	private void setH2Pass(final String h2Pass) {
		this.h2Pass = h2Pass;
	}

	/**
	 * Private setter for the database user
	 * 
	 * @param h2User
	 *            the h2User to set
	 */
	private void setH2User(final String h2User) {
		this.h2User = h2User;
	}

	/**
	 * Private setter for the indexingThread
	 * 
	 * @param indexThread
	 *            the indexThread to set
	 */
	private void setIndexThread(final IndexWriterThread indexThread) {
		this.indexThread = indexThread;
	}

	/**
	 * @param rootUserAddress
	 *            the rootUserAddress to set
	 */
	private void setRootUserAddress(InternetAddress rootUserAddress) {
		this.rootUserAddress = rootUserAddress;
	}

	/**
	 * Private setter for the {@link SessionFactory}
	 * 
	 * @param sessionFactory
	 *            the sessionFactory to set
	 */
	private void setSessionFactory(final SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	/** {@inheritDoc} */
	@Override
	public void shutdown() {

		if (!this.isAutoIndexing()) {
			this.getIndexThread().waitForFinish();
		}
		try {
			this.getConnection().close();
			this.getSessionFactory().close();
		} catch (SQLException e) {
			e.printStackTrace();
		}

	}

	/** {@inheritDoc} */
	@Override
	public void storeRootUser(Subject subject, InternetAddress address,
			UUID uuid) throws EdalException {

		Principal principal = null;

		for (final Principal p : subject.getPrincipals()) {
			principal = p;
			break;
		}
		if (principal == null) {
			throw new EdalException(
					"could not get the current pricipal from the authenticated subject");
		}

		Session session = this.getSession();
		Transaction transaction = session.beginTransaction();

		RootImplementation existingRoot = (RootImplementation) session
				.createCriteria(RootImplementation.class).uniqueResult();

		if (existingRoot != null) {
			session.delete(existingRoot);
		}

		RootImplementation newRoot = new RootImplementation(
				principal.getName(), principal.getClass().getSimpleName(),
				address, uuid.toString());

		session.save(newRoot);

		transaction.commit();

		session.close();

	}

	/** {@inheritDoc} */
	@Override
	public boolean validateRootUser(InternetAddress address, UUID uuid) {
		Session session = this.getSession();

		Transaction transaction = session.beginTransaction();

		RootImplementation rootUser = (RootImplementation) session
				.createCriteria(RootImplementation.class).uniqueResult();

		if (rootUser.getAddress().equals(address.getAddress())
				&& rootUser.getUuid().equals(uuid.toString())) {
			rootUser.setValidated(true);
			session.update(rootUser);
			transaction.commit();
			session.close();
			return true;
		} else {
			transaction.commit();
			session.close();
			return false;
		}

	}
}